<?php
/**
 * コードカバレッジ解析ライブラリ
 */
require_once '/../object.php';
require_once '/../Storage/binder.php';
require_once '/../Storage/diskunit.php';
if (PATH_SEPARATOR == ';') {
	require_once dirname(__FILE__) . '/../../Smarty/libs/Smarty.class.php';
}

/**
 * プログラム解析バインダー
 * @author admin
 *
 */
class ccv_binder extends Binder
{

}

/**
 * コードカバレッジ解析クラス
 */
class ccv extends object
{
	/** ストレージ保存用ラベル */
	const LABEL = 'CCV';
	/** やっちゃったフラグ */
	static protected $_done = FALSE;
	/** レポート */
	protected $_report = NULL;
	/** バインダー */
	public $binder = NULL;
	/** 解析結果TSVファイル出力フォルダー */
	protected $_storage;
	/** フィルターリスト */
	protected $_filterList = array();

	/**
	 * コンストラクタ
	 * プロセス内で身実行であれば、
	 */
	public function __construct(Storage $storage, $filterName = '')
	{
		parent::__construct();
		/*** xdebugが組み込まれていない場合なにもしないことにする */
		if (!function_exists('xdebug_start_code_coverage')) {
			return;
		}
		/*** 解析結果TSVファイル出力フォルダーを設定する */
		$this->_storage = $storage;
		/*** フィルターファイルから解析しないディレクトリを設定する */
		if ($filterName != '') {
			/*** フィルターファイルが消されていれば何もしない */
			if (file_exists($filterName)) {
				$this->_filterList = file($filterName, FILE_IGNORE_NEW_LINES);
			}
		}
		/*** すでに解析がスタートしている場合は、再スタートはしない */
		if (self::$_done === FALSE) {
			self::$_done = TRUE;
			xdebug_start_code_coverage();
		}
	}

	/**
	 * 解析
	 */
	public function analyze()
	{
		/*** バインダー準備 */
		$this->binder = new ccv_binder(self::LABEL);
		/*** 解析情報をもらう*/
		$analyzeReport = xdebug_get_code_coverage();
		/*** 実行プログラム所ジュールを一つづつ解析する */
		foreach ($analyzeReport as $programFileName => $passStepNums)
		{
			/*** フィルター対象ディレクトリはスキップする*/
			if ($this->_isFilter($programFileName)) {
				continue;
			}
			$analyzedModule = $this->analyzeProgramFile($programFileName, $passStepNums);
			$analyzedModule->outputHtml();
			$this->binder->bind($analyzedModule);
		}
	}

	/**
	 * フィルター対象であればTRUEを返す
	 * @param unknown $programFileName
	 */
	protected function _isFilter($programFileName)
	{
		foreach ($this->_filterList as $filterDir) {
			if (substr($programFileName, 0, strlen($filterDir)) == $filterDir) {
				return TRUE;
			}
		}
		return FALSE;
	}

	/**
	 * 実行プログラムモジュールをひとつづつ解析する
	 * @param unknown $programFileName
	 * @param unknown $programDoceds
	 */
	public function analyzeProgramFile($programFileName, $passStepNums)
	{
		if ($programFileName =='C:\pleiades432a\wks\hlib\Smarty\Components\Components.php') {
			$break = TRUE;
		}
		$dump = new ccv_analyzedModule($programFileName, $this->_storage);
		$passStepNumsCount = count($passStepNums);
		$i = 0;
		/*** 解析情報をダンプに反映する */
		foreach ($passStepNums as $stepNum => $passFlg) {
			$code = $dump->page($stepNum - 1);
			/*** 誤った行番号を指定された場合、パス日付は設置しない */
			if ($code !== FALSE) {
				$dump->page($stepNum - 1)->passFlg = new DateTime();
			}
		}
		return $dump;
	}

	/**
	 * コードカバレッジ解析済みモジュールリストが綴じられたバインダーを返す
	 * @return unknown
	 */
	public function getAnalyzedModuleList()
	{
		$filter = new Storage_Diskunit_ScanFilter();
		$filter->applicationNameFilterMethod = object::SELECT_AGREE;
		$retBinder = $this->_storage->scandir($filter);
		return $retBinder;
	}
}

/**
 * 解析プログラムモジュールクラス
 * @author admin
 *
 */
class ccv_analyzedModule extends Binder
{
	public function __construct($programFileName,Storage $storage,$update=TRUE)
	{
		parent::__construct($programFileName,$storage,$update);
		/*** 解析実績のないプログラムモジュールの場合、全段読み込む */
		if ($this->get_pagecount() == 0) {
			$this->initialize();
		} else if ($this->isUpdated()) {
			$this->initialize();
		}
	}

	/**
	 * 解析日時以降にソースファイルが更新されている場合ＴＲＵＥを返す
	 * @return boolean
	 */
	public function isUpdated()
	{
		$analyzedFlg = FALSE;
		$codeUpdate = new DateTime('@' . filemtime($this->_label));
		$codeCount = count($this->_pages);
		$i = 0;
		/*** 全行調査 */
		while ($i < $codeCount) {
			$code = $this->page($i+1);
			/*** パスフラグに日付が設定されていて */
			if (is_object($code)) {
				/*** 日付オブジェクトが設定されている */
				if (is_a($code->passFlg, 'datetime')) {
					/*** パスフラグに日付が設定されている行があれば解析実績有りと判断 */
					$analyzedFlg = TRUE;
					/*** ソースファイル更新日から過去のパスフラグがあれば即ＴＲＵＥ */
					if ($code->passFlg->diff($codeUpdate)->invert == 0) {
						return TRUE;
					}
				}
			} else {
				$a = 1;
			}
			$i ++;
		}
		/** 解析実績がない場合、ファイルが更新された物と判断する */
		if ($analyzedFlg === FALSE) {
			return TRUE;
		}
		return FALSE;
	}

	/**
	 * プログラムコードをソースファイルから設定する
	 * @return boolean
	 */
	public function initialize()
	{
		$this->_pages = array();
		$protramCodes = file($this->_label);
		/*** プログラムモジュールの読み込みに失敗したら終了 */
		if ($protramCodes === FALSE) {
			return FALSE;
		}

		/*** 全行追加 */
		foreach ($protramCodes as $num => $code) {
			$ccvCode = new ccv_program_code();
			$ccvCode->code = $code;
			$ccvCode->passFlg = '';
			$ccvCode->stepNum = $num + 1;
			$this->bind($ccvCode);
		}
	}

	/**
	 * CSV出力
	 * ファイル名は、プログラムファイル名.csv
	 */
	public function outputCsv()
	{
		$fp = fopen($this->_label . '.csv', 'w');
		if ($fp === FALSE) {
			return FALSE;
		}

		foreach ($this->_pages as $code) {
			$sjisCode = mb_convert_encoding($code->code, "sjis-win", "utf-8");
			$arr = array($code->stepNum,$code->passFlg,$sjisCode);
			$ret = fputcsv($fp, $arr);
			if ($ret === FALSE) {
				return FALSE;
			}
		}
		fclose($fp);
	}

	/**
	 * ＨＴＭＬ出力
	 */
	const TAB_SPACE = '&nbsp;&nbsp;&nbsp;&nbsp;';
	public function outputHtml()
	{
		$ccv = array();
		$i = 0;
		$codeCount = $this->get_pagecount();
		while ($i < $codeCount) {
			$code = $this->page($i);
			$source = htmlentities($code->code);
			$source = str_replace("\t", self::TAB_SPACE, $source);
			$source = str_replace(" ", '&nbsp;', $source);
			$passFlg = '';
			if (is_a($code->passFlg, 'datetime')) {
				$passFlg = 1;
			}
			$ccv[]
			= array(
					'stepnum' => $code->stepNum,
					'passflg' => $code->passFlg,
					'code' => $source,
			);
			$i ++;
		}

		$smarty = new Smarty();
		$smarty->force_compile = true;
		$smarty->template_dir = dirname(__FILE__);
		$smarty->compile_dir = dirname(__FILE__) . '/templates_c';
		$smarty->assign('ccv', $ccv);
		$html = $smarty->fetch('ccv.html');
		file_put_contents($this->_label . '.html', $html);
	}

	/**
	 * 処理コードを追加する
	 */
	public function bind(ccv_program_code $page)
	{
		return parent::bind($page);
	}
}

/**
 * プログラムコード
 * @author admin
 *
 */
class ccv_program_code extends object
{
	public $stepNum;
	public $passFlg;
	public $code;
}

/**
 * 単体テストコードブロック
 */
if (PATH_SEPARATOR == ';') {
include_once 'simpletest/unit_tester.php';
include_once 'simpletest/reporter.php';

if (class_exists('UnitTestCase')) {

class ccv_test extends UnitTestCase
{
	public function test1100()
	{
		$diskunit = new Storage_Diskunit('ccv', dirname(__FILE__).'/templates_c');
		$programFileName = 'C:\pleiades432\wks\hlib\CCV\test.php';
		$anl = new ccv_analyzedModule($programFileName, $diskunit);
		$anl->outputHtml();
	}

	public function test1200()
	{
		$storage = new Storage_Diskunit('ccv', 'C:\pleiades432\wks\hlib\CCV\templates_c');
		$ccv = new ccv($storage);
		$modules = $ccv->getAnalyzedModuleList();
		print_r($modules);
	}
}
}
}